package common.master;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import common.db.JdbcSource;
import common.db.dao.Dao;
import common.db.dao.DaoUtil;

import core.config.Env;
import core.config.Factory;

/**
 * マスターデータキャッシュ
 * @author Tadashi Nakayama
 *
 * @param <K> キー値
 * @param <V> バリュー値
 */
public abstract class MasterCache<K, V>  {

	/** キャッシュ */
	private final ConcurrentMap<K, V> cache = new ConcurrentHashMap<>();

	/** テーブルクラス */
	private final Class<V> table;

	/** 事前読込済フラグ */
	private boolean preloaded = false;

	/**
	 * コンストラクタ
	 * @param vals ダミー
	 */
	@SafeVarargs
	public MasterCache(final V... vals) {
		if (vals == null || vals.length != 0) {
			throw new IllegalArgumentException();
		}
		this.table = Factory.cast(vals.getClass().getComponentType());
	}

	/**
	 * 事前読み込み
	 */
	public final void preload() {
		if (isPreLoad() && Env.getEnv("MasterCache.preload", true)) {
			try (Dao dao = JdbcSource.getDao()) {
				for (final V val : dao.selectAll(getTableClass(), getQuery())) {
					this.cache.put(getKey(val), val);
				}
			}
			this.preloaded = true;
		}
	}

	/**
	 * クエリ取得
	 * @return クエリ
	 */
	protected final String getQuery() {
		return "SELECT * FROM " + DaoUtil.getTableName(this.table);
	}

	/**
	 * バリュー値取得
	 * @param key キー値
	 * @return バリュー値
	 */
	public final V get(final K key) {
		V ret = this.cache.get(key);
		if (ret == null) {
			if (!this.preloaded && !this.cache.containsKey(key)) {
				ret = getValue(key);
				if (this.cache.putIfAbsent(key, ret) != null) {
					ret = this.cache.get(key);
				}
			}
		}
		return ret;
	}

	/**
	 * テーブルクラス取得
	 * @return テーブルクラス
	 */
	protected final Class<V> getTableClass() {
		return this.table;
	}

	/**
	 * キー値からバリュー値を取得する
	 * @param key キー値
	 * @return バリュー値
	 */
	protected abstract V getValue(final K key);

	/**
	 * バリュー値からキー値を作成する
	 * @param val バリュー値
	 * @return キー値
	 */
	protected abstract K getKey(final V val);

	/**
	 * 事前読込判断
	 * @return 事前読込を行う場合 true を返す。
	 */
	protected abstract boolean isPreLoad();
}
